/*
 * Copyright (C) 2009 codemobiles.com.
 * http://www.codemobiles.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * author: Chaiyasit Tayabovorn 
 * email: chaiyasit.t@gmail.com
 */

package com.codemobiles.droidslator.activity;

import java.io.FileOutputStream;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnCancelListener;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;

import com.codemobiles.droidslator.R;
import com.codemobiles.droidslator.util.CameraManager;
import com.codemobiles.droidslator.util.CaptureLayout;
import com.itwizard.mezzofanti.OCR;

public class CameraPreviewActivity extends Activity implements SurfaceHolder.Callback, View.OnClickListener, Runnable {
  public static final String PACKAGE_NAME = "com.itwizard.mezzofanti";
  public static final String DATA_PATH = "/sdcard/tessdata/";
  public static final String RESULTS_PATH = DATA_PATH + "out/";
  public static final String TEMP_PATH = DATA_PATH + "temp/";
  public static final String PACKAGE_DATA = "/data/app/" + PACKAGE_NAME + ".apk";
  public static final String DOWNLOAD_URL = "http://www.itwizard.ro/mezzolang/";
  public static final String CONFIG_FILE = "config.txt";
  private Bitmap m_bmOCRBitmap; // bitmap we get the image from camera
  // public variables
  public static boolean m_bSkipIntroAtStartup = true; // skip the introduction
  // at startup (loaded from
  // the xml prefs-file)

  private boolean m_bHorizontalDisplay = true; // true=horizontal /
  // false=vertical (this is
  // updated all the time)
  private boolean m_bHorizDispAtPicTaken = true; // this is updated just before
  // the picture taken, it will
  // be sent as a param to the
  // OCR processing
  private boolean m_bScreenRequestPicture = false;
  private boolean m_bDidRegister = false; // was the broadcast receiver
  // registered or not
  private boolean m_bOCRInProgress = false; // if OCR is processing or not

  private boolean m_bLineMode = false; // capture-mode: line/all
  private String m_sOCRResultLineMode = ""; // the ocr string in line mode
  private CaptureLayout m_clCapture; // Capture-mode
  private ProgressDialog m_pdOCRInProgress; // display this progress dialog
  // while OCR runs
  private boolean m_bSdcardMounted = true; // checks if the sdcard is mounted or
  private boolean m_bPreviewReady = false; // is the preview ready
  private boolean m_bFocusButtonPressed = false; // focus button is pressed
  private boolean m_bHasSurface = false; // used to init the camera
  String m_sOCRResult;
  private ImageView cameraButton;
  private Context context;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    try {
      this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
      context = this;
      // never enter standby when camera open
      Window window = getWindow();
      window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
      CameraManager.Initialize(getApplication());
      setContentView(R.layout.camerapreview);
      cameraButton = (ImageView) findViewById(R.id.camera_requestphoto);
      initAllButtons();
      m_clCapture = (CaptureLayout) findViewById(R.id.mezzofanti_capturelayout_view);

    } catch (Exception ex) {
      System.out.println(ex.getMessage());
    } catch (Throwable t) {
      System.out.println(t.getMessage());
    }
  }

  private void initAllButtons() {
    cameraButton.setOnClickListener(new View.OnClickListener() {

      @Override
      public void onClick(View v) {
        m_bScreenRequestPicture = true;
        RequestCameraFocus();
      }
    });

  }

  @Override
  protected void onResume() {
    super.onResume();

    // remove ocr results
    m_bmOCRBitmap = null;
    System.gc();

    m_bSdcardMounted = true;

    // install an intent filter to receive SD card related events.
    IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
    intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
    intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
    intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
    intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
    intentFilter.addDataScheme("file");

    m_bDidRegister = true;

    // read the new settings, maybe the user changed something in results mode
    // so we store all the settings data in the local variables
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

    OCR.mConfig.GetSettings(prefs);
    m_bSkipIntroAtStartup = true;

    if (m_bSdcardMounted) {
      OCR.Initialize();
      OCR.get().SetLanguage(OCR.mConfig.GetLanguage());
    }

    if (m_bLineMode)
      CameraManager.SetImgDivisor(2);
    else
      CameraManager.SetImgDivisor(OCR.mConfig.GetImgDivisor());

    m_MezzofantiMessageHandler.sendEmptyMessage(R.id.mezzofanti_startCamera);

    // returned in Capture-mode
    SurfaceView surfaceView = (SurfaceView) findViewById(R.id.mezzofanti_preview_view);
    SurfaceHolder surfaceHolder = surfaceView.getHolder();
    if (m_bHasSurface) {
      // The activity was paused but not stopped, so the surface still exists.
      // Therefore
      // surfaceCreated() won't be called, so init the camera here.
      InitCamera(surfaceHolder);
    } else {
      // Install the callback and wait for surfaceCreated() to init the camera.
      surfaceHolder.addCallback(this);
      surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
  }

  private boolean RequestCameraFocus() {
    String ss[] = OCR.getLanguagesNative();
    if (ss.length == 0) {
      return false;
    }

    if (!m_bSdcardMounted)
      return false;
    if (m_bOCRInProgress == true)
      return false;

    m_bFocusButtonPressed = true;
    // m_clCapture.DrawFocusIcon(true, m_bHorizontalDisplay);
    CameraManager.get().RequestCameraFocus(m_MezzofantiMessageHandler);
    CameraManager.get().RequestAutoFocus();
    return true;
  }

  private void InitCamera(SurfaceHolder surfaceHolder) {
    if (CameraManager.get() == null)
      return;

    m_bPreviewReady = true;
    CameraManager.get().OpenDriver(surfaceHolder);
    CameraManager.get().StartPreview();
  }

  @Override
  public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    // TODO Auto-generated method stub

  }

  public void surfaceCreated(SurfaceHolder holder) {
    if (!m_bHasSurface) {
      // set the surface holder for the camera's use
      m_bHasSurface = true;
      SetCameraSurfaceHolder(holder);
      m_MezzofantiMessageHandler.sendEmptyMessage(R.id.mezzofanti_startCamera);
    }
  }

  // @Override
  public void surfaceDestroyed(SurfaceHolder holder) {
    m_bHasSurface = false;
  }

  @Override
  public void onClick(View v) {
    // TODO Auto-generated method stub

  }

  @Override
  public void run() {

    // called by the OCR thread
    int iPicWidth = m_bmOCRBitmap.getWidth();
    int iPicHeight = m_bmOCRBitmap.getHeight();
    int[] iImage = null;
    try {
      iImage = new int[iPicWidth * iPicHeight];
      m_bmOCRBitmap.getPixels(iImage, 0, iPicWidth, 0, 0, iPicWidth, iPicHeight);
    } catch (Exception ex) {
      m_bmOCRBitmap = null;
      System.gc();
    }

    m_bHorizDispAtPicTaken = true;
    if (iImage != null) {
      m_sOCRResult = OCR.get().ImgOCRAndFilter(iImage, iPicWidth, iPicHeight, m_bHorizDispAtPicTaken, m_bLineMode);
      // force free the mem
      iImage = null;
      m_bmOCRBitmap = null;
      System.gc();

      // bad results, get the (internal) image
      OCR.get().SaveMeanConfidence();
    } else {
      System.gc();
      m_bOCRInProgress = false;
      m_MezzofantiMessageHandler.sendEmptyMessage(R.id.mezzofanti_restartCaptureMode);
      return;
    }

    m_MezzofantiMessageHandler.sendEmptyMessage(R.id.mezzofanti_ocrFinished);
  }

  /*
   * ----------------------------------------------------------------------------
   * ------------------------ Events Handler
   * ------------------------------------
   * ----------------------------------------------------------------
   */

  /*
   * This call-back method is called from Camera.PictureCallback
   * m_PictureCallbackJPG = new Camera.PictureCallback() when photo is taken and
   * ready to capture.
   */
  private Handler m_MezzofantiMessageHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
      // ---------------------------------------------------------------
      case R.id.installactivity_killApp:
        android.os.Process.killProcess(android.os.Process.myPid());
        break;

      // ---------------------------------------------------------------
      case R.id.mezzofanti_restartCaptureMode:
        // we had a problem in the OCR thread, so we get back to capture mode
        // (mainly this is due to OutOfMemory exceptions)

        m_bmOCRBitmap = null;
        System.gc();

        if (m_bLineMode)
          m_clCapture.ShowWaiting("OCR Processing");
        else
          // if (m_pdOCRInProgress != null){
          m_pdOCRInProgress.dismiss();
        // }

        break;

      // ---------------------------------------------------------------
      case R.id.mezzofanti_startCamera:
        InitCamera();
        break;

      // ---------------------------------------------------------------
      case R.id.cameramanager_requestpicture:

        // do ocr
        Bitmap mBitmap = null;
        try {

          // save the file on disk
          FileOutputStream fs = new FileOutputStream(RESULTS_PATH + "img.jpg");
          fs.write((byte[]) msg.obj, 0, ((byte[]) msg.obj).length);
          fs.close();

          Rect frame = CameraManager.get().getFrame();

          int x = (int) (frame.left * 2.4);
          int y = (int) (frame.top * 2.4);

          /**
           * Check resolution and get the proper setting
           */
          DisplayMetrics displayMetrics = new DisplayMetrics();
          getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);

          int width;
          int height;
          
          if (displayMetrics.widthPixels > 700) {
            // 800 * 480 (Desire, Nexus)
            width = (int) (frame.width() * 1.3) - x * 2;
            height = (int) (frame.height() * 1.5);
          } else {
            // 320 * 480 (Magic, Legend)
            width = (int) (frame.width() * 2.2) - x * 2;
            height = (int) (frame.height() * 2.2);
          }

          Log.i("Droidslator: bitmap", "x:" + x + " y:" + y + " width:" + width + " height:" + height);

          // my modification
          mBitmap = BitmapFactory.decodeByteArray((byte[]) msg.obj, 0, ((byte[]) msg.obj).length);
          mBitmap = Bitmap.createBitmap(mBitmap, x, y, width, height, null, false);

          // int drawable = getResources().getIdentifier("test", "drawable",
          // "com.codemobiles.droidslator");
          // mBitmap = BitmapFactory.decodeResource(context.getResources(),
          // drawable);

          System.gc();
          Log.v("Droidslator LEK", "w=" + mBitmap.getWidth() + " h=" + mBitmap.getHeight());

          m_bLineMode = false;
          if (m_bLineMode) // we crop just the image of interest
            mBitmap = Bitmap.createBitmap(mBitmap, 256, 768 / 2 - 30, 512, 60, null, false);
          // otherwise, we use all image
        } catch (Throwable th) {

          m_bOCRInProgress = false;
          if (m_bLineMode) {
            // m_clCapture.ShowWaiting("");
          } else
            m_MezzofantiMessageHandler.sendEmptyMessage(R.id.mezzofanti_restartCaptureMode);
          break;
        }

        OCR.Initialize();
        OCR.get().SetLanguage(OCR.mConfig.GetLanguage());

        DoOCRJob(mBitmap);

        break;

      // ---------------------------------------------------------------
      case R.id.mezzofanti_ocrFinished:
        m_bOCRInProgress = false;
        if (!m_bLineMode) {
          // m_pdOCRInProgress.dismiss();
          System.gc();

          StopCamera();
          returnResults();
        } else {
          String padding = " ";
          if (m_sOCRResultLineMode.length() == 0)
            padding = "";

          String s = OCR.m_ssOCRResult.toString().replaceAll("[\r\n]+", "");
          while (s.length() > 0 && s.charAt(0) == ' ')
            s = s.substring(1);
          while (s.length() > 0 && s.charAt(s.length() - 1) == ' ')
            s = s.substring(0, s.length() - 1);

          m_sOCRResultLineMode = m_sOCRResultLineMode.concat(padding + s);

          // m_clCapture.ShowWaiting("");
          // m_clCapture.SetText(m_sOCRResultLineMode);
        }
        OCR.get().OCRClean();
        break;

      // ---------------------------------------------------------------
      case R.id.cameramanager_focus_succeded:
        if (!m_bFocusButtonPressed)
          return;
        // m_clCapture.DrawFocused(true, true);
        playSoundOnFocus();
        if (m_bScreenRequestPicture)
          RequestCameraTakePicture();
        m_bScreenRequestPicture = false;
        break;

      // ---------------------------------------------------------------
      case R.id.cameramanager_focus_failed:
        if (!m_bFocusButtonPressed)
          return;
        m_clCapture.DrawFocused(true, false);
        m_bScreenRequestPicture = false;
        break;

      default:
        break;

      }

      super.handleMessage(msg);
    }

  };

  private boolean RequestCameraTakePicture() {
    if (!m_bSdcardMounted)
      return false;
    if (m_bOCRInProgress == true)
      return false;

    m_bOCRInProgress = true;
    if (m_bPreviewReady) {
      m_bHorizDispAtPicTaken = m_bHorizontalDisplay;

      // m_clCapture.DrawFocused(false, false);
      // m_clCapture.DrawFocusIcon(false, m_bHorizontalDisplay);
      CameraManager.get().RequestPicture(m_MezzofantiMessageHandler);
      CameraManager.get().GetPicture();
      if (m_bLineMode)
        // m_clCapture.ShowWaiting(getString(R.string.mezzofanti_capturelayout_takingpicture));

        return true;
    }
    return false;
  }

  @Override
  public void onDestroy() {
    StopCamera();

    OCR ocr = OCR.get();
    if (ocr != null) {
      ocr.Destructor();
    }
    super.onDestroy();
  }

  private void returnResults() {
    if (m_bSdcardMounted) {
      Intent intent = new Intent(Intent.ACTION_VIEW);
      Bundle bun = new Bundle();
      // if not in line-mode, the activity will get the results directly from
      // the OCR results
      bun.putString("Result", m_sOCRResult);
      bun.putString("LineModeText", m_sOCRResultLineMode);
      bun.putBoolean("bLineMode", m_bLineMode);
      intent.putExtras(bun);
      setResult(RESULT_FIRST_USER, intent);
      finish();
    }

  }

  /*
   * ----------------------------------------------------------------------------
   * ------------------------ OCR Thread
   * ----------------------------------------
   * ------------------------------------------------------------
   */

  /**
   * Show a progress bar and start the OCR thread.
   * 
   * @param bm
   *          the bitmap to be OCR-ized.
   */
  public void DoOCRJob(Bitmap bm) {
    m_bmOCRBitmap = bm;

    m_pdOCRInProgress = ProgressDialog.show(this, "Text-recognition in progress", "Please wait...", true, true);
    m_pdOCRInProgress.setOnCancelListener(new OnCancelListener() {
      public void onCancel(DialogInterface dialog) {
        android.os.Process.killProcess(android.os.Process.myPid());
      }
    });

    Thread theOCRthread = new Thread(this);
    theOCRthread.start();
  }

  /**
   * Play a predefined sound, when camera focused.
   */
  private void playSoundOnFocus() {
    ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_SYSTEM, 100);
    tg.startTone(ToneGenerator.TONE_PROP_BEEP2);
  }

  private void StopCamera() {
    CameraManager.get().StopPreview();
    CameraManager.get().CloseDriver();
    m_bPreviewReady = false;
  }

  /**
   * Initialize the camera, open the driver (no params), we assume the surface
   * holder was set apriori with SetCameraSurfaceHolder.
   */
  private void InitCamera() {
    if (CameraManager.get() == null)
      return;

    m_bPreviewReady = true;
    if (CameraManager.get().OpenDriver()) {
      CameraManager.get().StartPreview();
    }
  }

  private void SetCameraSurfaceHolder(SurfaceHolder surfaceHolder) {
    if (surfaceHolder == null || CameraManager.get() == null)
      return;

    CameraManager.get().SetSurfaceHolder(surfaceHolder);
  }

}
